package wpmp.security.mgr.base;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.LogicalExpression;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.util.Assert;

import wpmp.security.mgr.utils.ReflectionUtils;

/**
 * 所有Dao类的基类
 * 
 * <pre>
 * 1.封装Hibernate的方法，简便操作
 * 2.需要注入 SessionFactory 对象
 * </pre>
 * 
 * @author Wayne.Wang<5waynewang@gmail.com>
 * @since 10:57:52 AM Dec 21, 2012
 * @param <T> 实体对应的类
 * @param <PK> 实体主键对应的类
 */
public class BaseDao<T, PK extends Serializable> {

	protected final Logger log = LoggerFactory.getLogger(getClass());

	protected SessionFactory sessionFactory;

	protected Class<T> entityClass;

	public BaseDao() {
		this.entityClass = ReflectionUtils.getSuperClassGenricType(getClass());
	}

	/**
	 * <pre>
	 * 获取DAO也可以这种方式，如：
	 * BaseDao<User, String> userDao = new BaseDao<User, String>(sessionFactory, User.class);
	 * </pre>
	 */
	public BaseDao(SessionFactory sessionFactory, Class<T> entityClass) {
		this.sessionFactory = sessionFactory;
		this.entityClass = entityClass;
	}

	/**
	 * 获取SessionFactory
	 */
	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	/**
	 * 采用@Autowired按类型注入SessionFactory, 当有多个SesionFactory的时候Override本函数.
	 */
	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	/**
	 * 获取当前Session
	 */
	public Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	/**
	 * 刷新当前Session
	 */
	public void flush() {
		getSession().flush();
	}

	/**
	 * 获取Jdbc Connection连接.
	 */
	public Connection getJdbcConnection() throws SQLException {
		return SessionFactoryUtils.getDataSource(sessionFactory).getConnection();
	}

	/**
	 * 获取JdbcTemplate实例
	 */
	public JdbcTemplate getJdbcTemplate() {
		return new JdbcTemplate(SessionFactoryUtils.getDataSource(sessionFactory));
	}

	/**
	 * 保存对象
	 */
	public void save(final T entity) {
		Assert.notNull(entity);
		getSession().saveOrUpdate(entity);
	}

	/**
	 * 批量保存对象
	 */
	public void save(final Collection<T> entities) {
		Assert.notNull(entities);

		final Session session = getSession();
		for (T entity : entities) {
			session.saveOrUpdate(entity);
		}
	}

	/**
	 * 删除对象.
	 * 
	 * @param entity 对象必须是session中的对象或含id属性的transient对象.
	 */
	public void delete(T entity) {
		Assert.notNull(entity);
		getSession().delete(entity);
	}

	/**
	 * 批量删除对象
	 * 
	 * @param entities
	 */
	public void delete(Collection<T> entities) {
		Assert.notNull(entities);

		final Session session = getSession();
		for (T entity : entities) {
			session.delete(entity);
		}
	}

	/**
	 * 批量删除对象
	 * 
	 * @param entities
	 */
	public void delete(T[] entities) {
		Assert.notNull(entities);

		final Session session = getSession();
		for (T entity : entities) {
			session.saveOrUpdate(entity);
		}
	}

	/**
	 * 根据ID删除对象
	 * 
	 * @param id
	 */
	public void delete(PK id) {
		Assert.notNull(id);
		delete(get(id));
	}

	/**
	 * 根据ID删除对象
	 * 
	 * @param id
	 */
	public int delete(PK[] ids) {
		Assert.notNull(ids);
		Query query = getSession().createQuery(
				"DELETE FROM " + entityClass.getSimpleName() + " WHERE " + getIdName() + " IN (:ids)");
		query.setParameterList("ids", ids);
		return query.executeUpdate();
	}

	/**
	 * load()方法可以充分利用二级缓存和内部缓存的现有数据，
	 */
	@SuppressWarnings("unchecked")
	public T load(PK id) {
		Assert.notNull(id);
		return (T) getSession().load(entityClass, id);
	}

	/**
	 * get()方法只在内部缓存中进行查找，如没有发现对应数据将跳过二级缓存，直接调用SQL完成查找
	 */
	@SuppressWarnings("unchecked")
	public T get(PK id) {
		Assert.notNull(id);
		return (T) getSession().get(entityClass, id);
	}

	/**
	 * 获取对象列表，支持排序
	 * 
	 * @param ids
	 * @param orders
	 * @return
	 */
	public List<T> queryForList(PK[] ids, Order... orders) {
		Assert.notNull(ids);
		Criteria criteria = createCriteria(Restrictions.in(getIdName(), ids));
		addOrder(criteria, orders);
		return queryForList(criteria);
	}

	/**
	 * 获取对象，支持排序
	 * 
	 * @param ids
	 * @param orders
	 * @return
	 */
	public List<T> queryForList(Collection<PK> ids, Order... orders) {
		Assert.notNull(ids);
		Criteria criteria = createCriteria(Restrictions.in(getIdName(), ids));
		addOrder(criteria, orders);
		return queryForList(criteria);
	}

	/**
	 * 获取全部对象
	 * 
	 * @return
	 */
	public List<T> queryForList() {
		return queryForList(createCriteria());
	}

	/**
	 * 获取全部对象，支持排序
	 * 
	 * @param orders
	 * @return
	 */
	public List<T> queryForList(Order... orders) {
		if (ArrayUtils.isEmpty(orders)) {
			return queryForList();
		}
		return queryForList(addOrder(createCriteria(), orders));
	}

	/**
	 * 获取全部对象，支持排序
	 * 
	 * @param orderBy 字段名，按此字段排序
	 * @param isAsc true升序，false降序
	 * @return
	 */
	public List<T> queryForList(String orderBy, boolean isAsc) {
		return queryForList(isAsc ? Order.asc(orderBy) : Order.desc(orderBy));
	}

	/**
	 * 获取全部对象，按orderBy字段升序
	 * 
	 * @param orderBy 字段名，按此字段排序
	 * @return
	 */
	public List<T> queryForList(String orderBy) {
		return queryForList(orderBy, true);
	}

	/**
	 * 根据Criterion获取对象列表
	 * 
	 * @param criterions
	 * @return
	 */
	public List<T> queryForList(Criterion... criterions) {
		return queryForList(createCriteria(criterions));
	}

	/**
	 * 根据Criteria获取对象列表
	 * 
	 * @param criteria
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<T> queryForList(Criteria criteria) {
		return criteria.list();
	}

	/**
	 * 按属性查找对象列表，匹配方式为相等，支持排序
	 * 
	 * @param propertyName
	 * @param value
	 * @param orders
	 * @return
	 */
	public List<T> queryForList(String propertyName, Object value, Order... orders) {
		Assert.hasText(propertyName);
		Criteria criteria = createCriteria(Restrictions.eq(propertyName, value));
		addOrder(criteria, orders);
		return queryForList(criteria);
	}

	/**
	 * 按属性查找唯一对象,匹配方式为相等
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public T queryForObject(String propertyName, Object value) {
		Assert.hasText(propertyName);
		return queryForObject(Restrictions.eq(propertyName, value));
	}

	/**
	 * 按Criteria查询唯一对象.
	 * 
	 * @param criterions 数量可变的Criterion
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public T queryForObject(Criterion... criterions) {
		return (T) createCriteria(criterions).uniqueResult();
	}

	/**
	 * 按Criteria查询唯一对象
	 * 
	 * @param criteria
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public T queryForObject(Criteria criteria) {
		return (T) criteria.uniqueResult();
	}

	/**
	 * 获取对象总数
	 * 
	 * @return
	 */
	public int queryForInt() {
		return queryForInt(createCriteria());
	}

	/**
	 * 根据Criteria获取对象数量
	 * 
	 * @param criteria
	 * @return
	 */
	public int queryForInt(Criteria criteria) {
		Object result = criteria.setProjection(Projections.rowCount()).uniqueResult();
		if (result == null) {
			return 0;
		}
		return ((Number) result).intValue();
	}

	/**
	 * 根据Criterion获取对象数量
	 * 
	 * @param criterions
	 * @return
	 */
	public int queryForInt(Criterion... criterions) {
		return queryForInt(createCriteria(criterions));
	}

	/**
	 * 判断对象某些属性的值在数据库中唯一. 不适合自定义主键
	 * 
	 * @param entity
	 * @param uniquePropertyNames 在POJO里不能重复的属性列表,以逗号分割 如"name,id" 如"name,id",表示(id+name)在数据库唯一
	 */
	public boolean isUnique(Object entity, String uniquePropertyNames) {
		return isUnique(entityClass, entity, uniquePropertyNames);
	}

	/**
	 * 判断对象某些属性的值在数据库中唯一
	 * 
	 * @param entityClass
	 * @param entity
	 * @param uniquePropertyNames
	 * @return
	 */
	public boolean isUnique(Class<T> entityClass, Object entity, String uniquePropertyNames) {
		Assert.hasText(uniquePropertyNames);
		Criteria criteria = createCriteria(entityClass).setProjection(Projections.rowCount());
		String[] nameList = uniquePropertyNames.split(",");
		try {
			// 循环加入唯一列
			for (String name : nameList) {
				criteria.add(Restrictions.eq(name, ReflectionUtils.invokeGetterMethod(entity, name)));
			}
			String idName = getIdName(entityClass);
			Object id = ReflectionUtils.invokeGetterMethod(entity, idName);
			// 如果id!=null,说明对象已存在,该操作为update,加入排除自身的判断
			if (id != null) {
				criteria.add(Restrictions.not(Restrictions.eq(idName, id)));
			}
		}
		catch (Exception e) {
			org.springframework.util.ReflectionUtils.handleReflectionException(e);
		}
		return (Integer) criteria.uniqueResult() == 0;
	}

	/**
	 * 判断对象某些属性的值在数据库中是否唯一(使用OR模式)
	 * 
	 * @param entity
	 * @param uniquePropertyNames 以","分隔属性名称.如"id,name" 如"id,name"表示id唯一或name都必须唯一
	 */
	public boolean isPropertyUnique(Object entity, String uniquePropertyNames) {
		return isPropertyUnique(entityClass, entity, uniquePropertyNames);
	}

	/**
	 * 判断对象某些属性的值在数据库中是否唯一(使用OR模式)
	 * 
	 * @param entityClass
	 * @param entity
	 * @param uniquePropertyNames
	 * @return
	 */
	public boolean isPropertyUnique(Class<T> entityClass, Object entity, String uniquePropertyNames) {
		Assert.hasText(uniquePropertyNames);
		Criteria criteria = createCriteria(entityClass).setProjection(Projections.rowCount());
		String[] nameList = uniquePropertyNames.split(",");
		try {
			// 循环加入唯一列
			Criterion[] criterions = new Criterion[nameList.length];
			for (int i = 0; i < nameList.length; i++) {
				criterions[i] = Property.forName(nameList[i]).eq(
						ReflectionUtils.invokeGetterMethod(entity, nameList[i]));
			}
			if (criterions.length == 1) {
				// 单条件查询,直接加入到查询语句中.
				criteria.add(criterions[0]);
			}
			else {
				// 两个以上的条件查询,先组织查询子句.
				LogicalExpression le = Restrictions.or(criterions[0], criterions[1]);
				for (int i = 2; i < criterions.length; i++) {
					le = Restrictions.or(le, criterions[i]);
				}
				criteria.add(le);
			}
			String idName = getIdName(entityClass);
			Object id = ReflectionUtils.invokeGetterMethod(entity, idName);
			if (id != null) criteria.add(Restrictions.ne(idName, id));
		}
		catch (Exception e) {
			org.springframework.util.ReflectionUtils.handleReflectionException(e);
		}
		return (Integer) criteria.uniqueResult() == 0;
	}

	/**
	 * 取得对象的主键名,辅助函数.
	 */
	private String getIdName(Class<?> clazz) {
		Assert.notNull(clazz);
		ClassMetadata meta = getSessionFactory().getClassMetadata(clazz);
		Assert.notNull(meta, "类 " + clazz + " 在hibernateSessionFactory中未定义");
		String idName = meta.getIdentifierPropertyName();
		Assert.hasText(idName, clazz.getSimpleName() + " 没有声明主键");
		return idName;
	}

	/**
	 * 取得对象的主键值,辅助函数.
	 */
	public Serializable getId(Class<?> entityClass, Object entity) throws NoSuchMethodException,
			IllegalAccessException, InvocationTargetException {
		Assert.notNull(entity);
		Assert.notNull(entityClass);
		return (Serializable) ReflectionUtils.getFieldValue(entity, getIdName(entityClass));
	}

	/**
	 * 创建Criteria对象
	 * 
	 * @param entityClass
	 * @param criterions
	 * @return
	 */
	public Criteria createCriteria(Class<T> entityClass, Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(entityClass);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	/**
	 * 创建Criteria
	 * 
	 * @param criterions
	 */
	public Criteria createCriteria(Criterion... criterions) {
		return createCriteria(entityClass, criterions);
	}

	/**
	 * 对Criteria添加Order
	 * 
	 * @param criteria
	 * @param orders
	 */
	public Criteria addOrder(Criteria criteria, Order... orders) {
		if (ArrayUtils.isEmpty(orders)) {
			return criteria;
		}
		for (Order order : orders) {
			criteria.addOrder(order);
		}
		return criteria;
	}

	/**
	 * 取得对象的主键名.
	 */
	public String getIdName() {
		ClassMetadata meta = getSessionFactory().getClassMetadata(entityClass);
		return meta.getIdentifierPropertyName();
	}
}